// ------------ directive
/**
 * @ngdoc overview
 * @name workflow.directives
 * @description
 *   工作流指令集
 */
angular.module('workflow.directives', [])

.directive('icErrorTip', function() {
  return {
    restrict: 'E',
    template: 
      '<div ng-if="error.notempty" class="assertive fss">“{{ title }}”不能为空</div>' +
      '<div ng-if="error.chinese" class="assertive fss">“{{ title }}”只能为中文</div>' +
      '<div ng-if="error.letter" class="assertive fss">“{{ title }}”只能为英文</div>' +
      '<div ng-if="error.num" class="assertive fss">“{{ title }}”只能为数字</div>' +
      '<div ng-if="error.idcard" class="assertive fss">“{{ title }}”只能为身份证</div>' +
      '<div ng-if="error.mobile" class="assertive fss">“{{ title }}”只能为手机号码</div>' +
      '<div ng-if="error.money" class="assertive fss">“{{ title }}”只能为金额</div>' +
      '<div ng-if="error.tel" class="assertive fss">“{{ title }}”只能为电话号码</div>' +
      '<div ng-if="error.zipcode" class="assertive fss">“{{ title }}”只能为邮政编码</div>' +
      '<div ng-if="error.email" class="assertive fss">“{{ title }}”只能为Email</div>',
    scope: {
      error: '=',
      title: '='
    }
  }
})

.controller('icCtrl', function($scope, icPatternByTypeFilter) {

  $scope.addPattern = function($ngModelCtrl, patternType) {
    function icPatternValidator(value) {
      var reg;
      var validaty = true;

      // 如果设置了验证规则
      if (patternType) {
        if (!value) {
          validaty = false;
        } else {
          reg = new RegExp(icPatternByTypeFilter(patternType));
          validaty = reg.test(value);
        }
      }

      $ngModelCtrl.$setValidity(patternType, validaty);
      return validaty ? value : undefined;
    }

    if($ngModelCtrl) {
      //For DOM -> model validation
      $ngModelCtrl.$parsers.unshift(icPatternValidator);
      //For model -> DOM validation
      $ngModelCtrl.$formatters.unshift(icPatternValidator);

      $scope.$error = $ngModelCtrl.$error;
    }
  }

})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:ic
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单控件指令
 *  目前 OA 支持工作流控件有以下类型：（目前移动端不支持列表控件、签章控件、进度条控件、图片上传控件、二维码控件）
 *  - 标签文本     label  
 *  - 单行输入框   text   
 *  - 多行输入框   textarea   
 *  - 下拉菜单     select   
 *  - 单选框       radio 
 *  - 复选框       checkbox 
 *  - 部门人员控件 user 
 *  - 日历控件     data 
 *  - 宏控件       auto 
 *  - 计算控件     calc 
 *  - 列表控件     listview
 *  - 签章控件     sign
 *  - 进度条控件   progressbar
 *  - 图片上传控件 imgupload
 *  - 文件上传控件 fileupload
 *  - 二维码控件   qrcode
 *
 *  控件通用字段说明：
 *  data-id:      控件 ID
 *  data-type:    控件类型
 *  data-title:   控件名（除标签外）
 *  data-value:   控件默认值
 *  origin-value: 控件保存的值
 *  各控件字段会有差别，具体看控件相关说明
 */
.directive('ic', function ($parse, $rootScope, $location, $compile, icPatternByTypeFilter, icFactory) {
  
  $rootScope.icModelCtrls = {}

  return {
    restrict: 'E',

    require: '?ngModel',

    controller: 'icCtrl',

    template: '<ic-error-tip error="$error" title="ic[\'data-title\']"></ic-error-tip>',

    compile: function(element, attr) {
      var absUrl = $location.absUrl()
      var icModelCtrls
      
      if(!$rootScope.icModelCtrls[absUrl]) {
        $rootScope.icModelCtrls[absUrl] = {}
      }
      
      icModelCtrls = $rootScope.icModelCtrls[absUrl]
    
      return function($scope, $element, $attrs, $ngModelCtrl) {
        var ic = $scope.ic = $parse(attr.data)($scope) || {};
        var ics = $parse(attr.ics)($scope) || [];

        $scope.getIc = function(icId) {
          return ics.filter(function(_ic) {
            return _ic['data-id'] == icId;
          })[0];
        };

        $scope.setValue = function(icId, value) {
          if (icModelCtrls[icId]) {
            icModelCtrls[icId].$setViewValue(value);
          }
        }

        $scope.getValue = function(icId) {
          // 如果是可写字段，则直接返回 model 的值
          if (icModelCtrls[icId]) {
            return icModelCtrls[icId].$viewValue;
          } else {
            var _ic = $scope.getIc(icId);
            return _ic ? _ic['origin-value'] : '';
          }
          return '';
        };

        if($ngModelCtrl) {
          icModelCtrls[ic['data-id']] = $ngModelCtrl;
          
          // @NOTE 自定义表单 后端返回了 data-value 且为空字符串
          // 进入ionic.bundle.js $watch方法 判断为false 所以需自定义undefined
          
          if (ic['data-type'] !== 'date') {
            $ngModelCtrl.$setViewValue(ic['origin-value'] || ic['data-value'] || undefined);
          }            

          $scope.addPattern($ngModelCtrl, $attrs.patternType)
        }

        var template = icFactory.parse(ic, $attrs.ngModel, $scope);
        var $content = $compile(template)($scope);

        $element.addClass('wfic wfic-' + ic['data-type']);
        $element.prepend($content);
      };
    }
  };
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icLabel
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单标签控件
 * 
 * @param  {expression}  data  控件数据
 */
.directive('icLabel', function() {
  return {
    restrict: 'E',
    replace: true,
    scope: {
      data: '='
    },
    template: '<span ng-style="getStyle()">{{ data["data-text"]  }}</span>',
    link: function($scope) {
      $scope.getStyle = function() {
        var ic = $scope.data
        return {
          'text-align': ic['data-align'],
          'font-weight': ic['data-bold'] == 1 ? '700' : '400',
          'font-size': ic['data-font-size'],
          'font-style': ic['data-italic'] == 1 ? 'italic' : 'normal',
          'text-decoration': ic['data-underline'] == 1 ? 'underline' : 'normal',
          'color': ic['data-color']
        }
      }
    }
  }
})

.constant('ROW_SPLITTER', /\n/g)

.constant('COL_SPLITTER', '`')

.constant('VAL_SPLITTER', ',')

.controller('icListViewCtrl', function($scope, $timeout, $ionicScrollDelegate, ROW_SPLITTER, COL_SPLITTER, VAL_SPLITTER) {
  var split = $scope.split = function(raw, splitter) {
    if(angular.isString(raw)) {
      return raw.split(splitter)
    }
    return []
  }

  // 以换行符分隔行，以`分隔列
  var splitRaws = $scope.splitRaws = function(raw) {
    var ret = []

    if(angular.isString(raw)) {
      var rawRows = $scope.split(raw, ROW_SPLITTER)
      angular.forEach(rawRows, function(rawRow) {
        if(rawRow) {
          ret.push($scope.split(rawRow, COL_SPLITTER))
        }
      })
    }

    return ret
  }

  $scope.rows = []

  // 拆分列配置
  $scope.lvTitles = split($scope.data['data-lv-title'], COL_SPLITTER)
  $scope.lvColtypes = split($scope.data['data-lv-coltype'], COL_SPLITTER)
  $scope.lvColvalues = split($scope.data['data-lv-colvalue'], COL_SPLITTER)
  $scope.lvSums = split($scope.data['data-lv-sum'], COL_SPLITTER)

  Array.prototype.push.apply($scope.rows, splitRaws($scope.data['origin-value']))

  $scope.scrollDeletage = $ionicScrollDelegate.$getByHandle('ic-listview-' + $scope.data['data-title'])

  $timeout(function() {
    var sv = $scope.scrollDeletage.getScrollView();
    var container = sv.__container;

    container.removeEventListener('touchstart', sv.touchStart);
    container.removeEventListener('mousedown', sv.mouseDown);
    document.removeEventListener('touchmove', sv.touchMove);
    document.removeEventListener('mousemove', sv.mouseMove);

    angular.forEach(
      [
        'touchStart',
        'mouseDown',
        'touchMove',
        'mouseMove'
      ],
      function(eventName) {
        var originalHandler = sv[eventName]
        sv[eventName] = function(e) {
          e.preventDefault = angular.noop
          originalHandler && originalHandler.call(sv, e)
        }
      }
    )

    container.addEventListener("touchstart", sv.touchStart, false);
    container.addEventListener("mousedown", sv.mouseDown, false);
    document.addEventListener("touchmove", sv.touchMove, false);
    document.addEventListener("mousemove", sv.mouseMove, false);
  });
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icListview
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单列表控件
 *  `data-lv-coltype`    列控件类型，多列间以 ` 分隔
 *  `data-lv-colvalue`   列值，多列间以 ` 分隔
 *  `data-lv-sum`        是否合计，1 为是，0为否，多列间以 ` 分隔
 *  `data-lv-title`      列标题，多列间以 ` 分隔
 * @param  {expression}  data  控件数据
 */
  .directive('icListview', function ($ionicModal, $rootScope, $location, COL_SPLITTER, VAL_SPLITTER) {

  return {
    restrict: 'E',
    replace: true,
    require: 'ngModel',
    controller: 'icListViewCtrl',
    scope: {
      data: '='
    },
    templateUrl: 'templates/workflow/ic-listview.html',
    link: function($scope, $element, $attr, $ngModelCtrl) {
      var icModelCtrls = $rootScope.icModelCtrls[$location.absUrl()]
      if (icModelCtrls) {
        icModelCtrls[$scope.data['data-id']] = $ngModelCtrl
      }
      $scope.modal = null
      $scope.edittingRow = {}
      // edittingRowIndex === -1 => 添加
      // edittingRowIndex > -1 => 编辑某一行
      $scope.edittingRowIndex = -1 

      $ionicModal.fromTemplateUrl('templates/workflow/ic-listview-modal.html', {
        scope: $scope
      })
      .then(function(modal) {
        $scope.modal = modal
      })

      // 将多行的数据组合为字符串返回
      $scope.joinRows = function(rows) {
        return rows.map(function(row) {
          return row.join(COL_SPLITTER)
        }).join('\n')
      }

      // 获取下拉选框类型的选项
      $scope.splitValue = function(colIndex) {
        return $scope.split($scope.lvColvalues[colIndex], VAL_SPLITTER)
      }

      // 添加一行
      $scope.addRow = function() {
        $scope.edittingRow = getRowDefalutValue()
        $scope.edittingRowIndex = -1
        $scope.modal && $scope.modal.show()
      }

      $scope.editRow = function(index) {
        $scope.edittingRow = angular.copy($scope.rows[index])
        $scope.edittingRowIndex = index
        $scope.modal && $scope.modal.show()
      }

      // 移除一行
      $scope.removeRow = function() {
        $scope.rows.splice($scope.edittingRowIndex, 1)
        // 没有行时重置回最左边
        if(!$scope.rows.length) {
          $scope.scrollDeletage.scrollTop(true)
        }
        $scope.modal && $scope.modal.hide()
      }

      $scope.save = function() {
        $scope.modal && $scope.modal.hide()

        if($scope.edittingRowIndex === -1) {
          $scope.rows.push($scope.edittingRow)
        } else {
          $scope.rows.splice($scope.edittingRowIndex, 1, $scope.edittingRow)
        }
      
        $scope.edittingRow = {}
        $scope.edittingRowIndex = -1
      }

      function getDefaultValue(colIndex) {
        var colType = $scope.lvColtypes[colIndex]
        var colValue = $scope.lvColvalues[colIndex]

        // 下拉选框、单选框转默认第一个
        if(['select', 'radio'].indexOf(colType) !== -1) {
          return colValue.split(VAL_SPLITTER)[0] || ''
        } else if(['text', 'textarea', 'date'].indexOf(colType) !== -1) {
          return colValue
        } 
        return ''
      }

      function getRowDefalutValue() {
        return $scope.lvColvalues.map(function(colValue, colIndex) {
          return getDefaultValue(colIndex)
        })
      }

      function updateViewValue(rows) {
        var viewValue = ''

        if(angular.isArray(rows) && rows.length) {
          viewValue = $scope.joinRows(rows)
        }

        $ngModelCtrl.$setViewValue(viewValue)
        $ngModelCtrl.$render()
      }

      $scope.$watch('rows', updateViewValue, true)
    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icListviewView
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单列表控件查看

 * @param  {expression}  data  控件数据
 */
.directive('icListviewView', function() {
  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    controller: 'icListViewCtrl',
    scope: {
      data: '=',
      onRowClick: '='
    },
    templateUrl: 'templates/workflow/ic-listview-view.html',
    link: angular.noop
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icListviewCalc
 * @element  ANY
 * @restrict A
 * @description
 *  工作流表单列表控件 - 计算类型
 *  
 * @param  {expression}  exp             计算公式
 * @param  {expression}  rowData         行数据
 */
.directive('icListviewCalc', function() {
  return {
    restrict: 'E',
    require: 'ngModel',
    replace: true,
    template: '<input type="hidden" />',
    scope: {
      exp: '=',
      rowData: '='
    },
    link: function($scope, $element, $attr, $ngModelCtrl) {
      var reg = /[\d+]/g

      $scope.__parseInt = function(str) {
        return parseFloat(str)
      }
      if(angular.isString($scope.exp)) {
        var watchExp = $scope.exp.replace(/\[(\d+)\]/g, function(match, num) {
          // 下标=序号-1
          return '__parseInt(rowData[' + (num - 1) + '])'
        })

        $scope.$watch(watchExp, function(val) {
          if(angular.isUndefined(val)) {
            val = watchExp
          } else if(isNaN(val)) {
            val = 0
          }

          $ngModelCtrl.$setViewValue(val)
          $ngModelCtrl.$render()
        })
      } else {
        throw new Error('列表控件计算公式出错')
      }
    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icListviewCalc
 * @element  ANY
 * @restrict A
 * @description
 *  工作流表单列表控件 - 复选框组
 *  
 * @param  {expression}  list            可选值列表  
 */
.directive('icListviewCheckbox', function(VAL_SPLITTER) {
  return {
    restrict: 'E',
    require: 'ngModel',
    template: 
      '<label ng-repeat="item in list" class="checkbox"> ' +
        '<input type="checkbox" ng-model="item.checked" ng-change="updateViewValue()"> ' +
        '{{ item.value }}' +
      '</label>',
    scope: {
      values: '='
    },
    link: function($scope, $element, $attr, $ngModelCtrl) {
      $scope.list = []

      $scope.$watch('values', function(values) {
        var viewValue = $ngModelCtrl.$viewValue
        var checkedArr = angular.isString(viewValue) ? viewValue.split(VAL_SPLITTER) : []

        values = values || ''

        $scope.list = values.split(VAL_SPLITTER).map(function(val) {
          return {
            value: val,
            checked: checkedArr.indexOf(val) !== -1
          }
        })
      })

      $scope.updateViewValue = function() {
        var newValue = $scope.list.filter(function(item) {
          return item.checked
        }).map(function(item) {
          return item.value
        }).join(VAL_SPLITTER)

        $ngModelCtrl.$setViewValue(newValue)
      }
    }
  }
})


/**
 * @ngdoc controller
 * @name  workflow.directives.controller:IcImguploadCtrl
 * @description
 *   图片上传控件相关控制器
 */
.controller('IcImguploadCtrl', function($scope, $window, attrFromTemplateFilter) {
  // 图片最大宽适配节点宽度
  $scope.setSizeAdjustWidth = function(parentElem) {
    var dataWidth = $scope.data['data-width']
    var dataHeight = $scope.data['data-height']
    var clientWidth = parentElem.clientWidth

    if(dataWidth && dataHeight) {
      if(dataWidth > clientWidth) {
        $scope.width = clientWidth
        $scope.height = dataHeight / dataWidth * clientWidth
      } else {
        $scope.width = dataWidth
        $scope.height = dataHeight
      }
    }
  }

  $scope.initAutoSizeAdjust = function(parentElem) {
    $scope.setSizeAdjustWidth(parentElem)
    $window.addEventListener('resize', function() {
      $scope.setSizeAdjustWidth(parentElem)
      $scope.$applyAsync()
    }, false)
  }

  $scope.hasSrc = function() {
    return $scope.localSrc || $scope.originSrc
  }

  try {
    $scope.originSrc = attrFromTemplateFilter($scope.data.value, 'data-src')
  } catch(e) {
    $scope.originSrc = ''
    console.error('ic-imgupload: 获取原始图片地址失败')
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icImgupload
 * @element  ANY
 * @restrict E
 * @todo 验证规则未实现
 * @description
 *  工作流表单图片上传控件
 *  
 * @param  {expression}  data            控件数据
 */
.directive('icImgupload', function(Utils) {
  return {
    restrict: 'E',
    require: 'ngModel',
    scope: {
      data: '='
    },
    controller: 'IcImguploadCtrl',
    replace: true,
    templateUrl: 'templates/workflow/ic-imgupload.html',
    link: function($scope, $element, $attr, $ngModelCtrl) {
      // 初始值设置为空文件对象
      // 此处由于后端在做判断时，如果没有拿到文件对象会将字段清空，而导致没有编辑图片直接保存时
      // 图片会被删除
      // @Todo: 讨论现在这种处理方式是否不妥
      $ngModelCtrl.$setViewValue(new File([], ''))

      $scope.updateFile = function(e) {
        var file = e.target.files[0]
        if(file) {
          $ngModelCtrl.$setViewValue(file)
          Utils.getImagePreviewUrl(file, function(src) {
            $scope.localSrc = src
            $scope.$applyAsync()
          })
        }
      }

      $scope.initAutoSizeAdjust($element[0])
    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icImguploadView
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单图片上传控件预览
 *  
 * @param  {expression}  data            控件数据
 */
.directive('icImguploadView', function() {
  return {
    restrict: 'E',
    scope: {
      data: '='
    },
    controller: 'IcImguploadCtrl',
    replace: true,
    templateUrl: 'templates/workflow/ic-imgupload-view.html',
    link: function($scope, $element, $attr) {
      $scope.initAutoSizeAdjust($element[0])
    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icFileupload
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单文件上传控件
 *  
 * @param  {expression}  data            控件数据
 */
.directive('icFileupload', function($window, FileUtils, fullUrlFilter) {
  return {
    restrict: 'E',
    require: 'ngModel',
    controller: 'icCtrl',
    scope: {
      data: '=',
      name: '='
    },
    replace: true,
    templateUrl: 'templates/workflow/ic-fileupload.html',
    link: function($scope, $element, $attr, $ngModelCtrl) {
      if($scope.data['origin-value'] && $scope.data.value) {
        var dataset = angular.element($scope.data.value)[0].dataset
        $scope.file = angular.extend({}, dataset)
      }
      $scope.selectFile = function(e) {
        var file = e.target.files[0]
        if(file) {
          $ngModelCtrl.$setViewValue(file)
          $scope.file = {
            iseditable: '1',
            read: '1',
            name: file.name,
            type: FileUtils.getExtname(file.name),
            src: '',
            value: ''
          }
          // 事件监听value值, 需在上传成功后清空, 防止删除后重新上传相同文件失败
          e.target.value = ''
          $ngModelCtrl.$setValidity('notempty', true)
          $scope.$applyAsync()
        }
      }

      $scope.removeFile = function() {
        delete $scope.file
        $ngModelCtrl.$setViewValue(null)
        if ($attr.patternType == 'notempty') {
            $ngModelCtrl.$error.notempty = true
            $scope.addPattern($ngModelCtrl, $attr.patternType)
        }
      }

      $scope.download = function() {
        $window.location.href = fullUrlFilter($scope.file.src)
      }

      // 文件名为空，不能算有了吧
      if ($scope.hasOwnProperty('file') && $scope.file.name == '') {
          delete $scope.file
      }

      // 上传控件没必要去检查其他，检查为空就好了
      if ($attr.patternType == 'notempty' && $scope.hasOwnProperty('file') && $scope.file.src != undefined && $scope.file.src != '') {
        $ngModelCtrl.$setValidity('notempty', true)
        $scope.$applyAsync()
      } else {
        $scope.addPattern($ngModelCtrl, $attr.patternType)
      }
    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icFileuploadView
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单图片上传控件
 *  
 * @param  {expression}  data            控件数据
 */
.directive('icFileuploadView', function($window, FileUtils, fullUrlFilter) {
  return {
    restrict: 'E',
    scope: {
      data: '='
    },
    replace: true,
    templateUrl: 'templates/workflow/ic-fileupload-view.html',
    link: function($scope, $element, $attr) {
      if($scope.data['origin-value'] && $scope.data.value) {
        var dataset = angular.element($scope.data.value)[0].dataset
        $scope.file = angular.extend({}, dataset)
      }

      $scope.download = function() {
        $window.location.href = fullUrlFilter($scope.file.src)
      }
    }
  }
})

.directive('icFileuploadHidden', function() {
  return {
    restrict: 'E',
    require: 'ngModel',
    scope: {
      data: '=',
      file: '=',
      name: '=',
      value: '=ngModel'
    },
    replace: true,
    template: '<div><input type="hidden" value="{{ value }}" name="{{ name }}"/></div>',
    link: function($scope, $element, $attr, $ngModelCtrl) {
      $scope.$watch(function() {
        return $scope.data['origin-value']
      }, function(val) {
        $ngModelCtrl.$setViewValue(val)
      })

      $scope.$watch('file', function(file) {
        if(file) {
          $ngModelCtrl.$setViewValue(file.name)
        } else if(file === null) {
          $ngModelCtrl.$setViewValue('')
        }
      })
    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icSign
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单签章手写控件
 *  
 * @param  {expression}  data            控件数据
 */
.directive('icSign', function($q, $window, $ionicModal, $ionicLoading, IbPopup, CommonApi) {
  var cid = 0

  // 获取图片有效区域
  function getDrawImageRect(cvs, rgba/* rgba a=a*255 */) { 
    var rgba = rgba || [0, 0, 0, 0]
    var data = cvs.getContext('2d').getImageData(0, 0, cvs.width, cvs.height).data

    var offsetLeft
    var offsetRight
    var offsetTop
    var offsetBottom

    for(var x = 0; x < cvs.width; x++) {
      for(var y = 0; y < cvs.height; y++) {
        var index = (cvs.width * y + x) * 4,
            cr = data[index],
            cg = data[index + 1]
            cb = data[index + 2]
            ca = data[index + 3]

        if(rgba[0] !== cr || rgba[1] !== cg || rgba[2] !== cb || rgba[3] !== ca) {
          if(typeof offsetLeft === 'undefined') {
            offsetLeft = offsetRight = x
            offsetTop = offsetBottom = y
          } else {
            offsetLeft = Math.min(x, offsetLeft)
            offsetRight = Math.max(x, offsetRight)
            offsetTop = Math.min(y, offsetTop)
            offsetBottom = Math.max(y, offsetBottom)
          }
        }
      }
    }

    return {
      offsetLeft: offsetLeft,
      offsetRight: offsetRight,
      offsetTop: offsetTop,
      offsetBottom: offsetBottom,
      // 由于 offset 是下标，实现计算像素数时要 +1
      width: offsetRight - offsetLeft + 1,
      height: offsetBottom - offsetTop + 1
    }
  }

  function getDataURI(id) {
    var cvs = document.getElementById(id)
    var PADDING = 10
    var dataURL = ''

    if(cvs) {
      var rect = getDrawImageRect(cvs, [255, 255, 255, 255])
      var tempCvs = document.createElement('canvas')
      var tempCtx = tempCvs.getContext('2d')

      tempCvs.width = rect.width + PADDING * 2
      tempCvs.height = rect.height + PADDING * 2
      tempCtx.drawImage(cvs, rect.offsetLeft, rect.offsetTop, rect.width, rect.height, PADDING, PADDING, rect.width, rect.height)
      
      dataURL = tempCvs.toDataURL()
      tempCvs = tempCtx = null
    }
    return dataURL
  }

  return {
    restrict: 'E',
    require: 'ngModel',
    controller: 'icCtrl',
    scope: {
      data: '=',
      src: '=ngModel',
      patternType: '@'
    },
    replace: true,
    templateUrl: 'templates/workflow/ic-sign.html',
    link: function($scope, $element, $attr, $ngModelCtrl) {
      var customCanvasId = 'icSignCanvas' + cid++

      // 全屏显示，所以用 window.innerWidth 及 innerHeight

      $scope.pwCanvas = {
        options: {
          undo: true,
          width: $window.innerWidth,
          height: $window.innerHeight,
          lineWidth: 5,
          color: $scope.data['data-sign-color'] || '#000',
          customCanvasId: customCanvasId
        },
        version: 0
      }

      $ionicModal.fromTemplateUrl('templates/workflow/ic-sign-modal.html', {
        scope: $scope
      })
      .then(function(modal) {
        $scope.modal = modal
        $scope.modal.hideDelay = true
      })

      // 重置
      $scope.reset = function() {
        // 当版本为 0 时，此方法相当于取消功能，直接关闭窗口
        if($scope.pwCanvas.version === 0) {
          $scope.modal.hide()
        } else {
          $scope.pwCanvas.version = 0
        }
      }

      // 完成
      $scope.done = function() {
        if($scope.pwCanvas.version !== 0) {
          var dataURI = getDataURI(customCanvasId)
          $ionicLoading.show()
          CommonApi.uploadBase64Img(dataURI)
          .success(function(res) {
            if(res.isSuccess) {
              $ngModelCtrl.$setViewValue(res.host + res.data)
              $ngModelCtrl.$render()
              $scope.modal.hide()
            } else {
              IbPopup.tip('手写签名保存失败，请重试')
            }
          })
          .error(function() {
            IbPopup.tip('手写签名保存失败，请重试')
          })
          .finally(function() {
            $ionicLoading.hide()
            $scope.reset()
          })
        } else {
          $scope.modal.hide()
        }
      }

      $scope.remove = function() {
        $ngModelCtrl.$setViewValue('')
        $ngModelCtrl.$render()
      }

      $scope.addPattern($ngModelCtrl, $attr.patternType)

      if($scope.data['origin-value']) {
        $ngModelCtrl.$setViewValue($scope.data['origin-value'])
      }

    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:icScript
 * @element  ANY
 * @restrict E
 * @description
 *  工作流表单自定义脚本执行
 *  
 * @param  {expression}  data            自定义脚本
 */
.directive('icScript', function($document, IbPopup) {
  return {
    restrict: 'E',
    scope: {
      data: '='
    },
    link: function($scope) {
      var document = $document[0]
      var hasExec = false

      $scope.$watch('data', function(data) {
        if(data && !hasExec) {
          try {
            var script = document.createElement('script')
            script.innerHTML = $scope.data
            document.body.appendChild(script)
          } catch(e) {
            IbPopup.tip('自定义脚本执行出错')
          }
          hasExec = true
        }
      })
    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:wfProcess
 * @element  ANY
 * @restrict E
 * @description
 *  工作流步骤列表
 *  
 * @param  {expression}  process         步骤数据
 */
.directive('wfProcess', function() {
  return {
    restrict: 'E',
    replace: true,
    scope: {
      process: '='
    },
    templateUrl: 'templates/workflow/wf-process.html'
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:wfIcValueFields
 * @element  ANY
 * @restrict E
 * @description
 *  工作流有值字段显示
 *  
 * @param  {expression}  fields         工作流表单控件组
 * @param  {expression}  hidden         保密字段
 */
.directive('wfIcValueFields', function() {
  return {
    restrict: 'E',
    replace: true,
    scope: {
      fields: '=',
      hiddenField: '='
    },
    templateUrl: 'templates/workflow/wf-ic-value-fields.html',
    link: function($scope) {
      // 判断是否保密字段
      $scope.isHidden = function(item) {
        if(angular.isString($scope.hiddenField) && $scope.hiddenField) {
          return $scope.hiddenField.split(',').indexOf(item['data-id']) !== -1;
        }
        return false;
      }
    }
  }
})

/**
 * @ngdoc directive
 * @name workflow.directives.directive:wfIcEmptyFields
 * @element  ANY
 * @restrict E
 * @description
 *  工作流空值字段显示
 *  
 * @param  {expression}  fields         工作流表单控件组
 * @param  {expression}  hidden         保密字段
 */
.directive('wfIcEmptyFields', function($ionicScrollDelegate) {
  return {
    restrict: 'E',
    // replace: true,
    scope: {
      fields: '='
    },
    templateUrl: 'templates/workflow/wf-ic-empty-fields.html',
    link: function($scope) {
      $scope.emptyFieldShown = false
      $scope.toggleFieldShown = function() {
        $scope.emptyFieldShown = !$scope.emptyFieldShown
        $ionicScrollDelegate.resize()
      }
    }
  }
});

//@Note:
// issue: https://forum.ionicframework.com/t/when-using-horizontal-scroll-ion-scroll-page-cant-be-scrolled-down-in-y-direction/3833/22?u=andmar8
// from: https://codepen.io/rajeshwarpatlolla/pen/xGWBja
// -> https://github.com/ionic-team/ionic/pull/2606
(function() {
  var HorizontalScrollFix = (function() {
    function HorizontalScrollFix($timeout, $ionicScrollDelegate) {
      return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          var mainScrollID = attrs.horizontalScrollFix,
              scrollID = attrs.delegateHandle;
          
          var getEventTouches = function(e) {
            return e.touches && (e.touches.length ? e.touches : [
              {
                pageX: e.pageX,
                pageY: e.pageY
              }
            ]);
          };
          
          var fixHorizontalAndVerticalScroll = function() {
            var mainScroll, scroll;
            mainScroll = $ionicScrollDelegate.$getByHandle(mainScrollID).getScrollView();
            scroll = $ionicScrollDelegate.$getByHandle(scrollID).getScrollView();
            
            // patch touchstart
            scroll.__container.removeEventListener('touchstart', scroll.touchStart);
            scroll.touchStart = function(e) {
              var startY;
              scroll.startCoordinates = ionic.tap.pointerCoord(e);
              if (ionic.tap.ignoreScrollStart(e)) {
                return;
              }
              scroll.__isDown = true;
              if (ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT') {
                scroll.__hasStarted = false;
                return;
              }
              scroll.__isSelectable = true;
              scroll.__enableScrollY = true;
              scroll.__hasStarted = true;
              scroll.doTouchStart(getEventTouches(e), e.timeStamp);
              startY = mainScroll.__scrollTop;
              
              // below is our changes to this method
              // e.preventDefault();
             
              // lock main scroll if scrolling horizontal
              $timeout((function() {
                var animate, yMovement;
                yMovement = startY - mainScroll.__scrollTop;
                if (scroll.__isDragging && yMovement < 2.0 && yMovement > -2.0) {
                  mainScroll.__isTracking = false;
                  mainScroll.doTouchEnd();
                  animate = false;
                  return mainScroll.scrollTo(0, startY, animate);
                } else {
                  return scroll.doTouchEnd();
                }
              }), 100);
            };
            scroll.__container.addEventListener('touchstart', scroll.touchStart);
          };
          $timeout(function() { fixHorizontalAndVerticalScroll(); });          
        }
      };
    }

    return HorizontalScrollFix;

  })();

  angular.module('workflow.directives')
  .directive('horizontalScrollFix', ['$timeout', '$ionicScrollDelegate', HorizontalScrollFix]);

}).call(this);
